﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO;
using System.Runtime.InteropServices;
using EasyHook;
using System.Windows.Forms;
using System.Drawing.Win32;
using System.Diagnostics;

namespace HoldemHook
{

    public class Injector : EasyHook.IEntryPoint
    {
        //?getDC@QWidget@@UBEPAUHDC__@@XZ

        public InjectClient Interface = null;
        public LocalHook CreateFileHook = null;
        public LocalHook ExtTextOutHook = null;
        public LocalHook QPainter_drawTextHook = null;
        public LocalHook QWidget_getDC = null;
        public LocalHook QWidgetPrivate_repaint_sysHook=null;

        public static StreamWriter Log = null;

        public GUINodeMap GuiNodes = new GUINodeMap();

        Stack<EventArgs> Queue = new Stack<EventArgs>();

        public Injector(
            RemoteHooking.IContext InContext,
            String InChannelName)
        {
            if(null==Log)
                Log = new StreamWriter("Injector.log");
            Log.AutoFlush = true;
            Log.WriteLine("Log Started {0}",DateTime.Now);
            Interface = RemoteHooking.IpcConnectClient<InjectClient>(InChannelName);

            Interface.Ping(RemoteHooking.GetCurrentProcessId());
        }

        public void Run(
            RemoteHooking.IContext InContext,
            String InArg1)
        {
            try
            {
                Log.WriteLine("Injector.Run {0}", InArg1);

                ExtTextOutHook = LocalHook.Create(
                    LocalHook.GetProcAddress("gdi32.dll", "ExtTextOutW"),
                    new DExtTextOutW(ExtTextOutW_Hooked),
                    this);

                ExtTextOutHook.ThreadACL.SetExclusiveACL(new Int32[1]);

                Log.WriteLine("ExtTextOutW hooked");

#if false
                QWidgetPrivate_repaint_sysHook = LocalHook.Create(
                    LocalHook.GetProcAddress("QtGui4.dll", "?drawWidget@QWidgetPrivate@@QAEXPAVQPaintDevice@@ABVQRegion@@ABVQPoint@@HPAVQPainter@@PAVQWidgetBackingStore@@@Z"),
                    new D_QWidgetPrivate_repaint_sys(QWidgetPrivate_repaint_sys_Hooked),
                    this);

                QWidgetPrivate_repaint_sysHook.ThreadACL.SetExclusiveACL(new Int32[1]);

                Log.WriteLine("QWidgetPrivate::repaint_sys Hooked");
#endif
                

                QPainter_drawTextHook  = LocalHook.Create(
                    LocalHook.GetProcAddress("QtGui4.dll", "?drawTextItem@QPainter@@QAEXABVQPointF@@ABVQTextItem@@@Z"),
                        new D_QPainter_drawTextItem(
                            QPainter_drawTextItem_Hooked), this);

                QPainter_drawTextHook.ThreadACL.SetExclusiveACL(new Int32[1]);
                Log.WriteLine("QPainter::drawText() hooked");
#if false
                /*
                IntPtr lib=NativeAPI.LoadLibrary("InjectNative.dll");
                IntPtr fun = NativeAPI.GetProcAddress(lib, "QPainter_drawTextItem");
                if (fun != IntPtr.Zero) 
                {
                    QPainter_drawTextHook = LocalHook.CreateUnmanaged(LocalHook.GetProcAddress("QtGui4.dll", "?drawTextItem@QPainter@@QAEXABVQPointF@@ABVQTextItem@@@Z"),
                        fun, IntPtr.Zero);
                    QPainter_drawTextHook.ThreadACL.SetExclusiveACL(new Int32[1]);
                }*/
                QWidget_getDC = LocalHook.Create(
                    LocalHook.GetProcAddress("QtGui4.dll", "?getDC@QWidget@@UBEPAUHDC__@@XZ"),
                        new D_QWidget_getDC(
                            QWidget_getDC_Hooked), this);

                QWidget_getDC.ThreadACL.SetExclusiveACL(new Int32[1]);
                Log.WriteLine("QWidget::getDC() hooked");
#endif
            }
            catch (Exception e)
            {
                /*
                    Now we should notice our host process about this error...
                 */
                Interface.ReportError(RemoteHooking.GetCurrentProcessId(), e);
                Log.WriteLine("Exception in Injector.Run {0}", e);
                return;
            }


            // wait for host process termination...
            try
            {
                Log.Write("Getting process...");
                Process myProcess = Process.GetCurrentProcess();
                IntPtr hMainWnd = myProcess.MainWindowHandle;
                Log.WriteLine("PID={0}, hMainWnd={1}", myProcess.Id, hMainWnd);

                // send enum windows
                EnumWindows enuWnd = new EnumWindows();
                enuWnd.GetWindows(hMainWnd);

                WinWnd mainWnd = new WinWnd(hMainWnd);
                Log.WriteLine("MAIN WINDOW ({1} childs): {0}", mainWnd,enuWnd.Items.Count);

                Interface.FireWindowEvent(new WindowEventArgs(WindowEventType.Add, myProcess.Id, mainWnd, null));

                foreach (WinWnd wnd in enuWnd.Items)
                {
                    Log.WriteLine("WINDOW: {0}",wnd);
                    Interface.FireWindowEvent(new WindowEventArgs(WindowEventType.Add, myProcess.Id, wnd, enuWnd.Items));
                }

                while (Interface.Ping(RemoteHooking.GetCurrentProcessId()))
                {
                    Log.WriteLine("Waiting queue...");
                    Log.Flush();
                    Thread.Sleep(500);

                    // transmit newly monitored file accesses...
                    lock (Queue)
                    {
                        while (Queue.Count > 0)
                        {
                            EventArgs args = Queue.Pop();
                            //Log.WriteLine("InjectedCall: {0}", args);
                            if (args is CallEventArgs)
                            {
                             //   args.Process = RemoteHooking.GetCurrentProcessId();
                                Interface.FireCallEvent(args as CallEventArgs);
                            }
                            else if (args is GUINodeEventArgs)
                            {
                                Interface.FireGUINodeEvent(args as GUINodeEventArgs);
                            }
                        }
                    }
                }
                Log.WriteLine("Exiting Injector...");
            }
            catch (Exception e)
            {
                Log.WriteLine("Exiting Injector on exception: {0}", e);
                // NET Remoting will raise an exception if host is unreachable
            }
        }
        #region
  /*      [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        static extern IntPtr CreateFile(
            String InFileName,
            UInt32 InDesiredAccess,
            UInt32 InShareMode,
            IntPtr InSecurityAttributes,
            UInt32 InCreationDisposition,
            UInt32 InFlagsAndAttributes,
            IntPtr InTemplateFile);*/
        #endregion
        #region CreateFile
        [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet=CharSet.Unicode, SetLastError=true)]
        delegate IntPtr DCreateFile(
            String InFileName,
            UInt32 InDesiredAccess,
            UInt32 InShareMode,
            IntPtr InSecurityAttributes,
            UInt32 InCreationDisposition,
            UInt32 InFlagsAndAttributes,
            IntPtr InTemplateFile);

        delegate void DCreateFileAsync(
            Int32 InClientPID,
            IntPtr InHandle,
            String InFileName,
            UInt32 InDesiredAccess,
            UInt32 InShareMode,
            IntPtr InSecurityAttributes,
            UInt32 InCreationDisposition,
            UInt32 InFlagsAndAttributes,
            IntPtr InTemplateFile);

     

        // this is where we are intercepting all file accesses!
        static IntPtr CreateFile_Hooked(
            String InFileName,
            UInt32 InDesiredAccess,
            UInt32 InShareMode,
            IntPtr InSecurityAttributes,
            UInt32 InCreationDisposition,
            UInt32 InFlagsAndAttributes,
            IntPtr InTemplateFile)
        {
            try
            {
                Injector This = (Injector)HookRuntimeInfo.Callback;

                lock (This.Queue)
                {
                    if(This.Queue.Count < 1000)
                        This.Queue.Push(new CallEventArgs("CreateFile",null, InFileName, InDesiredAccess, InShareMode, InSecurityAttributes, InCreationDisposition, InFlagsAndAttributes, InTemplateFile));
                }
            }
            catch
            {
            }

            // call original API...
            return Api.CreateFile(
                InFileName,
                InDesiredAccess,
                InShareMode,
                InSecurityAttributes,
                InCreationDisposition,
                InFlagsAndAttributes,
                InTemplateFile);
        }
        #endregion

        #region ExtTextOutW

        [UnmanagedFunctionPointer(CallingConvention.StdCall,
         CharSet = CharSet.Auto,
          SetLastError = true)]
        delegate bool DExtTextOutW(
            IntPtr hdc,
            int X,
            int y,
            uint fuOptions,
            [In] ref IntPtr lprc,
            string lpString,
            uint cbCount,
            [In] IntPtr lpDx
            );

        

        // this is where we are intercepting all file accesses!
        static bool ExtTextOutW_Hooked(
            IntPtr hdc,
            int X,
            int Y,
            uint fuOptions,
            [In] ref IntPtr lprc,
            string lpString,
            uint cbCount,
            [In] IntPtr lpDx)
        {
            try
            {
                Injector This = (Injector)HookRuntimeInfo.Callback;
                
                Log.WriteLine("* ExtTextOutW "+lpString);

                IntPtr hwnd = Api.WindowFromDC(hdc);
                
                Log.WriteLine(" WindowFromDC({0})={1}", hdc, hwnd); 
                
                lock (This.Queue)
                {
                    if (This.Queue.Count < 1000)
                        This.Queue.Push(new CallEventArgs("ExtTextOutW", null, hwnd, hdc, X, Y, fuOptions, lprc, lpString, cbCount, lpDx));
                }
            }
            catch
            {
            }

            // call original API...
            return Api.ExtTextOutW(
                 hdc,
                 X,
                 Y,
                 fuOptions,
                 ref lprc,
                 lpString,
                 cbCount,
                 lpDx
                  );

        }
        #endregion

        [StructLayout(LayoutKind.Sequential, Pack=8)]
        public struct QTextItem
        {
            System.Int32 a0;
            System.Int32 a1;
            System.Int32 a2;
            System.Int32 a3;
            System.Int32 a4;
            System.Int32 a5;
            System.Int32 a6;
            System.Int32 a7;
            public System.Int32 len;
            [MarshalAs(UnmanagedType.LPWStr, SizeParamIndex = 1)]
            public string str;
        };

        [StructLayout(LayoutKind.Sequential, Pack = 8,Size=32)]
        public struct QPointF
        {
            public System.Double x;
            public System.Double y;
        };

        [StructLayout(LayoutKind.Sequential, Size=4)]
        public struct QString
        {
            public IntPtr data;
        };

        [StructLayout(LayoutKind.Sequential, Pack = 8, CharSet=CharSet.Unicode)]
        public struct WIDGETINFO
        {
            [MarshalAs(UnmanagedType.LPWStr, SizeParamIndex = 1)]
            public string title;
            [MarshalAs(UnmanagedType.LPWStr, SizeParamIndex = 1)]
            public string name;
            [MarshalAs(UnmanagedType.LPStr, SizeParamIndex = 1)]
            public string clazz;
            public System.Double x;
            public System.Double y;
            public System.Double w;
            public System.Double h;
        }

        static string widgetInfoString(IntPtr widget)
        {
            String result = "";
            WIDGETINFO wi = new WIDGETINFO();
            while (widget != IntPtr.Zero)
            {
                result += widget;
                widget = QWidget_describe(widget, ref wi);
                result += " CLASS(" + wi.clazz + ")NAME(" + wi.name + ")TITLE(" + wi.title + ")";
                result += "(" + wi.x + "," + wi.y + ",w=" + wi.w + ",h=" + wi.h + ")";
                result += " -> ";
                //Log.WriteLine("Widget-Parent "+widget+";"+(widget==IntPtr.Zero));
            }
            return result;
        }

        [DllImport ("QtGui4.dll",  CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.ThisCall,
            EntryPoint = "?drawTextItem@QPainter@@QAEXABVQPointF@@ABVQTextItem@@@Z")]  // Unicode
        public static extern void QPainter_drawTextItem(IntPtr obj, IntPtr pt, IntPtr ti);

        [UnmanagedFunctionPointer(CallingConvention.ThisCall,
         CharSet = CharSet.Unicode, SetLastError = true)]
        delegate void D_QPainter_drawTextItem(IntPtr obj, IntPtr pt, IntPtr ti);

        
        static void QPainter_drawTextItem_Hooked( IntPtr obj,  IntPtr  pt,  IntPtr  ti)
        { 

            QTextItem qti = (QTextItem)Marshal.PtrToStructure
                (ti, typeof(QTextItem));

            QPointF qpt = (QPointF)Marshal.PtrToStructure
                (pt, typeof(QPointF));
            
            IntPtr widget = QPainter_getWidget(obj); 

            Log.Write("* QPainter::drawTextItem("+qti.str+";("+qpt.x+","+qpt.y+");");
            Log.WriteLine(" with widget="+widgetInfoString(widget));


            try 
            {
                Injector This = (Injector)HookRuntimeInfo.Callback;

                lock (This.Queue)
                {
                    WIDGETINFO wi = new WIDGETINFO();
                    List<GUINode> nodes = new List<GUINode>();
                    int handle = widget.ToInt32();
                    while (widget != IntPtr.Zero)
                    {
                        widget = QWidget_describe(widget, ref wi);
                        GUINode guiNode = new GUINode();
                        guiNode.Handle = handle;
                        guiNode.Bounds = new System.Drawing.RectangleF((float)wi.x, (float)wi.y, (float)wi.w, (float)wi.h);
                        guiNode.Class = wi.clazz + ":" + wi.name;
                        guiNode.Title = wi.title;
                        guiNode.ParentHandle = widget.ToInt32();
                        nodes.Add(guiNode);
                        handle = widget.ToInt32();
                    }
                 
                    for (int i = 0; i < nodes.Count - 1; i++)
                    {
                        nodes[i].Parent = nodes[i + 1];
                    }

                    for (int i = 0; i < nodes.Count; i++)
                    {
                        if (!This.GuiNodes.Contains(nodes[i].Handle))
                        {
                            This.GuiNodes.Add(nodes[i]);
                            This.Queue.Push(new GUINodeEventArgs(nodes[i]));
                        }
                    }

                    if (This.Queue.Count < 1000)
                        This.Queue.Push(new CallEventArgs("QPainter::drawtextItem", null,qti.str, qpt.x, qpt.y, handle));
                }
            }
            catch
            {
            }


            QPainter_drawTextItem(  obj,   pt,   ti);
        }

#if false
        [DllImport("QtGui4.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.ThisCall,
    EntryPoint = "?getDC@QWidget@@UBEPAUHDC__@@XZ")]  // Unicode
        public static extern IntPtr QWidget_getDC_real(IntPtr obj);

        [UnmanagedFunctionPointer(CallingConvention.ThisCall,
         CharSet = CharSet.Unicode, SetLastError = true)]
        delegate IntPtr D_QWidget_getDC(IntPtr obj);

        static IntPtr QWidget_getDC_Hooked(IntPtr obj)
        {
            IntPtr dc = QWidget_getDC_real(obj);

            Log.WriteLine(string.Format("* QWidget::getDC()={0:x}",dc));


            try
            {
                Injector This = (Injector)HookRuntimeInfo.Callback;

                lock (This.Queue) 
                {
                    if (This.Queue.Count < 1000)
                        This.Queue.Push(new CallEventArgs("QWidget::getDC", dc));
                }
            }
            catch
            {
            }
            return dc;

            
        }

            [DllImport("QtGui4.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.ThisCall,
    EntryPoint = "?drawWidget@QWidgetPrivate@@QAEXPAVQPaintDevice@@ABVQRegion@@ABVQPoint@@HPAVQPainter@@PAVQWidgetBackingStore@@@Z")]  // Unicode
        public static extern void QWidgetPrivate_repaint_sys_real(IntPtr obj, IntPtr qpdev, IntPtr qrgn, IntPtr qpt,int i,IntPtr qpaint,IntPtr qbacksto);

        [UnmanagedFunctionPointer(CallingConvention.ThisCall,
         CharSet = CharSet.Unicode, SetLastError = true)]
        delegate void D_QWidgetPrivate_repaint_sys(IntPtr obj, IntPtr qpdev, IntPtr qrgn, IntPtr qpt,int i,IntPtr qpaint,IntPtr qbacksto);

        static void QWidgetPrivate_repaint_sys_Hooked(IntPtr obj, IntPtr qpdev, IntPtr qrgn, IntPtr qpt,int i,IntPtr qpaint,IntPtr qbacksto)
        {
            Log.WriteLine(string.Format("*==> QWidget::drawWidget {{ "+obj));
            IntPtr qwidget = QWidgetPrivate_qfunc(obj);
            Log.WriteLine("+qfunc "+ qwidget);
            IntPtr q2 = Marshal.ReadIntPtr(obj, 4);
            Log.WriteLine("+q2 " + q2);
            string wtitle = QWidget_title(qwidget);
            Log.WriteLine("+windowtitle "+wtitle);
            /*IntPtr data = Marshal.ReadIntPtr(wtitle);
            Log.WriteLine("+dataptr "+data);
            IntPtr chrs = Marshal.ReadIntPtr(data, 12);
            Log.WriteLine("+charptr "+chrs);
            string s = Marshal.PtrToStringUni(chrs);
            Log.WriteLine("title="+s);*/
            QWidgetPrivate_repaint_sys_real(obj, qpdev, qrgn, qpt,i,qpaint,qbacksto);

            Log.WriteLine(string.Format("*<== QWidget::drawWidget}}"));
        }


        [DllImport("QtGui4.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.ThisCall,
    EntryPoint = "?q_func@QWidgetPrivate@@AAEPAVQWidget@@XZ")]  // Unicode
        public static extern IntPtr QWidgetPrivate_qfunc(IntPtr obj);

        [DllImport("QtGui4.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.ThisCall,
    EntryPoint = "?windowTitle@QWidget@@QBE?AVQString@@XZ")]  // Unicode
        public static extern QString QWidget_windowTitle(IntPtr obj);
#endif
        [DllImport("InjectNative.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall,
            EntryPoint = "QWidget_describe")]  // Unicode
        public static extern IntPtr QWidget_describe(IntPtr obj, ref WIDGETINFO info);
        
        [DllImport("InjectNative.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall,
            EntryPoint = "QPainter_getWidget")]  // Unicode
        public static extern IntPtr QPainter_getWidget(IntPtr obj);
   
    }
}
